package com.example.framework.web.core.util;

import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ObjectUtil;
import com.example.framework.common.exception.ServiceException;
import com.example.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.example.framework.common.util.json.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

@Validated // @Valid 不行
@Slf4j
public class RestUtils {

    private static OkHttpClient okHttpClient;

    @Resource
    public void setOkHttpClient(OkHttpClient okHttpClient) {
        RestUtils.okHttpClient = okHttpClient;
    }

    private static final String DEFAULT_CONTENT_TYPE = org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

    @Nullable
    public static <Type> Type get(@NotBlank String url, @NotNull Class<Type> returnType) {
        return get(url, null, null, returnType);
    }

    @Nullable
    public static <Type> Type get(@NotBlank String url, @NotNull TypeReference<Type> typeReference) {
        return get(url, null, null, typeReference);
    }

    @Nullable
    public static <Type> Type get(@NotBlank String url, Map<String, String> paramMap, @NotNull Class<Type> returnType) {
        return get(url, null, paramMap, returnType);
    }

    @Nullable
    public static <Type> Type get(@NotBlank String url, Map<String, String> paramMap, @NotNull TypeReference<Type> typeReference) {
        return get(url, null, paramMap, typeReference);
    }

    @Nullable
    public static <Type> Type get(@NotBlank String url, Map<String, String> headerMap, Map<String, String> paramMap, @NotNull Class<Type> returnType) {
        Request.Builder builder = new Request.Builder();
        handleRequestHeaderAndParam(builder, url, headerMap, paramMap);

        return sendRequest(builder.build(), returnType);
    }

    @Nullable
    public static <Type> Type get(@NotBlank String url, Map<String, String> headerMap, Map<String, String> paramMap, @NotNull TypeReference<Type> typeReference) {
        Request.Builder builder = new Request.Builder();
        handleRequestHeaderAndParam(builder, url, headerMap, paramMap);

        return sendRequest(builder.build(), typeReference);
    }

    /**
     * POST 请求
     *
     * @param url         url 不能为空
     * @param requestBody 请求体，可空
     * @param returnType  响应体类型，不能为空
     * @param <Type>      响应结果类型，不能为空
     * @return 响应结果
     */
    @Nullable
    public static <Type> Type post(@NotBlank String url, Object requestBody, @NotNull Class<Type> returnType) {
        return post(url, null, null, null, requestBody, returnType, false);
    }

    @Nullable
    public static <Type> Type post(@NotBlank String url, Object requestBody, @NotNull TypeReference<Type> typeReference) {
        return post(url, null, null, null, requestBody, typeReference, false);
    }

    /**
     * 含请求头和请求参数的 POST 请求
     *
     * @param url         url 不能为空
     * @param headerMap   请求头，可空
     * @param paramMap    请求参数，可空
     * @param requestBody 请求体，可空
     * @param returnType  响应体类型，不能为空
     * @param <Type>      响应结果类型，不能为空
     * @return 响应结果
     */
    @Nullable
    public static <Type> Type post(@NotBlank String url, Map<String, String> headerMap, Map<String, String> paramMap, Object requestBody, @NotNull Class<Type> returnType) {
        return post(url, headerMap, null, paramMap, requestBody, returnType, false);
    }

    @Nullable
    public static <Type> Type post(@NotBlank String url, Map<String, String> headerMap, Map<String, String> paramMap, Object requestBody, @NotNull TypeReference<Type> typeReference) {
        return post(url, headerMap, null, paramMap, requestBody, typeReference, false);
    }

    /**
     * 文件上传 POST 请求
     *
     * @param url         url 不能为空
     * @param contentType 文件的 contentType，不能为空
     * @param file        文件，不能为空
     * @param returnType  响应体类型，不能为空
     * @param <Type>      响应结果类型，不能为空
     * @return 响应结果
     */
    @Nullable
    public static <Type> Type postFile(@NotBlank String url, @NotBlank String contentType, @NotNull File file, @NotNull Class<Type> returnType) {
        return post(url, null, contentType, null, file, returnType, true);
    }

    @Nullable
    public static <Type> Type postFile(@NotBlank String url, @NotBlank String contentType, @NotNull File file, @NotNull TypeReference<Type> typeReference) {
        return post(url, null, contentType, null, file, typeReference, true);
    }

    /**
     * 含请求头和请求参数的文件上传 POST 请求
     *
     * @param url         url 不能为空
     * @param headerMap   请求头，可空
     * @param contentType 文件的 contentType，不能为空
     * @param paramMap    请求参数，可空
     * @param file        文件，不能为空
     * @param returnType  响应体类型，不能为空
     * @param <Type>      响应结果类型，不能为空
     * @return 响应结果
     */
    @Nullable
    public static <Type> Type postFile(@NotBlank String url, Map<String, String> headerMap, @NotBlank String contentType, Map<String, String> paramMap, @NotNull File file, @NotNull Class<Type> returnType) {
        return post(url, headerMap, contentType, paramMap, file, returnType, true);
    }

    @Nullable
    public static <Type> Type postFile(@NotBlank String url, Map<String, String> headerMap, @NotBlank String contentType, Map<String, String> paramMap, @NotNull File file, @NotNull TypeReference<Type> typeReference) {
        return post(url, headerMap, contentType, paramMap, file, typeReference, true);
    }

    /**
     * 发送 POST 请求
     *
     * @param url         url 不能为空
     * @param headerMap   请求头，可空
     * @param contentType contentType，默认为 application/json，如果是文件，则不能为空
     * @param paramMap    请求参数，可空
     * @param requestBody 请求体，可空
     * @param returnType  响应体类型，不能为空
     * @param isFile      是否为文件，是则需要指定 contentType
     * @param <Type>      响应结果类型，不能为空
     * @return 响应结果
     */
    @Nullable
    private static <Type> Type post(@NotBlank String url, Map<String, String> headerMap, String contentType, Map<String, String> paramMap, Object requestBody, @NotNull Class<Type> returnType, boolean isFile) {
        Request request = buildRequest(url, headerMap, contentType, paramMap, requestBody, isFile);
        return sendRequest(request, returnType);
    }

    @Nullable
    private static <Type> Type post(@NotBlank String url, Map<String, String> headerMap, String contentType, Map<String, String> paramMap, Object requestBody, @NotNull TypeReference<Type> typeReference, boolean isFile) {
        Request request = buildRequest(url, headerMap, contentType, paramMap, requestBody, isFile);
        return sendRequest(request, typeReference);
    }

    private static Request buildRequest(@NotBlank String url, Map<String, String> headerMap, String contentType, Map<String, String> paramMap, Object requestBody, boolean isFile) {
        Request.Builder builder = new Request.Builder();
        handleRequestHeaderAndParam(builder, url, headerMap, paramMap);
        // 处理请求体
        RequestBody body;
        if (Objects.isNull(requestBody)) {
            body = RequestBody.create("", null);
        } else {
            if (isFile) {
                Assert.hasText(contentType, "必须指定文件的contentType");
                body = RequestBody.create((File) requestBody, MediaType.get(contentType));
            } else {
                body = RequestBody.create(JsonUtils.toJsonString(requestBody), MediaType.get(ObjectUtil.isNotEmpty(contentType) ? contentType : DEFAULT_CONTENT_TYPE));
            }
        }
        builder.post(body);
        return builder.build();
    }

    private static void handleRequestHeaderAndParam(Request.Builder builder, String url, Map<String, String> headerMap, Map<String, String> paramMap) {
        // 请求头处理
        if (ObjectUtil.isNotEmpty(headerMap)) {
            builder.headers(Headers.of(headerMap));
        }

        // 请求参数处理
        if (ObjectUtil.isEmpty(paramMap)) {
            builder.url(url);
        } else {
            UrlBuilder urlBuilder = UrlBuilder.of(url);
            paramMap.forEach(urlBuilder::addQuery);
            builder.url(urlBuilder.build());
        }
    }

    @SuppressWarnings("unchecked")
    private static <Type> Type sendRequest(Request request, Class<Type> returnType) {
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (response.body() != null) {
                String responseBody = response.body().string();
                // 如果为String，responseBody是不带引号的，用jackson转对象会失败
                if (returnType == String.class) {
                    return (Type) responseBody;
                }
                return JsonUtils.parseObject(responseBody, returnType);
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("请求出现异常, e: {}", e.getMessage());
            throw ServiceException.of(GlobalErrorCodeConstants.REST_CALL_FAILED);
        }
    }

    private static <Type> Type sendRequest(Request request, TypeReference<Type> typeReference) {
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (response.body() != null) {
                String responseBody = response.body().string();
                return JsonUtils.parseObject(responseBody, typeReference);
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("请求出现异常, e: {}", e.getMessage());
            throw ServiceException.of(GlobalErrorCodeConstants.REST_CALL_FAILED);
        }
    }

}
